package pl.smath.regex.tree;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import pl.smath.regex.exceptions.ParseError;
import pl.smath.regex.tree.vertexes.LabelVertex;
import pl.smath.regex.tree.vertexes.MinusVertex;
import pl.smath.regex.tree.vertexes.NumVertex;
import pl.smath.regex.tree.vertexes.RelationVertex;
import pl.smath.regex.tree.vertexes.SumIteratorVertex;
import pl.smath.regex.tree.vertexes.SumVertex;
import pl.smath.regex.tree.vertexes.VarVertex;
import pl.smath.regex.tree.vertexes.Vertex;
import pl.smath.regex.tree.vertexes.pair.AbsPairVertex;
import pl.smath.regex.tree.vertexes.pair.ExponentVertex;
import pl.smath.regex.tree.vertexes.pair.FractionVertex;
import pl.smath.regex.tree.vertexes.pair.PlusVertex;
import pl.smath.regex.tree.vertexes.pair.ProductVertex;

public class Parser {

	private Map<String, String> specialSymbols;
	
	public Parser(Map<String,String> special) {
		specialSymbols = special;
	}
	
	public Parser() {
		specialSymbols = new HashMap<String, String>();
	}
	
	/*
	 * Parse in order:
	 * 0 - sums
	 * 1 - consts, vars, numbers, locals (ie. iterators)
	 * 2 - brackets
	 * 3 - subtraction
	 * 4 - raising to a power
	 * 5 - multiplication
	 * 6 - division
	 * 7 - addition
	 * 
	 * On this level we do not allow relations!
	 */
	private Vertex parse(Tape tape) throws ParseError {
		parseSum(tape);
		
		parseNumbers(tape, "Number");
		parseVariables(tape, "Variable"); 
		parseConst(tape);
		parseSpecial(tape);
		
		parseBrackets(tape);
		
		parseMinus(tape);
		
		parseOperator(tape, "^", "Exponent", new ExponentVertex(null, null));	
		parseOperator(tape, "*", "Product", new ProductVertex(null, null));
		parseOperator(tape, "/", "Fraction", new FractionVertex(null, null));
		parseOperator(tape, "+", "Plus", new PlusVertex(null, null));
			
		if (tape.size() == 1 && !tape.elementAt(0).isString())
			return tape.elementAt(0).getVertex();
		
		throw new ParseError("Parse not found.");
	}
	
	private void parseMinus(Tape tape) throws ParseError {
		String op = "-";
		TapePosition pos = tape.indexOf(op);
		int index = pos.getFirst();
		
		while (!pos.notFound()) {
			String elem = tape.elementAt(index).toString();
			if (elem.equals(op)) {
				// minus without surroundings
				if (index > 0) {
					// not first symbol from the left - we do put plus inside
					Token right = tape.elementAt(index+1);
					if (right.isString()) 
						throw new ParseError("Wrong negative value.");
					Vertex minus = new MinusVertex(right.getVertex());
					tape.change(index, new Token("+"));
					tape.change(index+1, new Token(minus));
				} else {
					// first symbol from the left - we do not put plus inside
					Token right = tape.elementAt(index+1);
					if (right.isString()) 
						throw new ParseError("Wrong negative value.");
					Vertex minus = new MinusVertex(right.getVertex());
					tape.remove(index);
					tape.change(index, new Token(minus));
				}
			} else {
				// minus with surroundings [must be the first symbol from the left, we do not allow notation '-+']
				if (!elem.endsWith("-")) 
					throw new ParseError("Wrong negative construction.");
				
				Token right = tape.elementAt(index+1);
				if (right.isString()) 
					throw new ParseError("Wrong negative value.");
				Vertex minus = new MinusVertex(right.getVertex());
				
				tape.delete(pos);
				tape.change(index+1, new Token(minus));
			}
			
			
			pos = tape.indexOf(op);
			index = pos.getFirst();
		}
	}
	
	private void parseOperator(Tape tape, String op, String name, AbsPairVertex vertex) throws ParseError {
		TapePosition pos = tape.indexOf(op);
		int index = pos.getFirst();
		while (!pos.notFound()) {
			String elem = tape.elementAt(index).toString();
			if (!elem.equals(op)) 
				throw new ParseError(name + " found in no expected situation.");
			
			try {
				Token left = tape.elementAt(index-1);
				Token right = tape.elementAt(index+1);
				if (left.isString() || right.isString())
					throw new ParseError("Wrong " + name + " surrounding.");
				AbsPairVertex exp = vertex.new_instance();
				exp.setLeft(left.getVertex());
				exp.setRight(right.getVertex());
				tape.change(index-1, index+2, new Token(exp));
			} catch (IndexOutOfBoundsException e) {
				throw new ParseError("Wrong " + name + " surrounding.");
			}	
			
			pos = tape.indexOf(op);
			index = pos.getFirst();
		}
	}
	

	/*
	 * Assuming sums are not nested! 
	 */
	private void parseSum(Tape tape) throws ParseError {
		TapePosition opening = tape.indexOf("Sum");
		if (opening.notFound())
			return;
		
		TapePosition closing = tape.indexOf("}", opening);
		closing.setSecond(closing.getSecond()+1);
		closing = tape.indexOf("}", closing);
		
		// between opening and closing we have full sum entry 
		
		while (!opening.notFound()) {
			if (closing.notFound() || opening.getFirst() != closing.getFirst())
				throw new ParseError("Sum expresion invalid.");
			 
			closing.setSecond(closing.getSecond()+1);          // moving after bracket
			closing = new TapePosition(tape.splitAt(closing)); // splitting part before bracket
			opening = new TapePosition(tape.splitAt(opening)); // cutting sum
			
			String expresion = tape.elementAt(opening.getFirst()).toString();
			Token paste = new Token(new SumVertex(expresion));
			tape.change(opening.getFirst(), paste);
			
			opening = tape.indexOf("Sum", closing);
			if (!opening.notFound()) {
				closing = tape.indexOf("}", opening);
				closing = tape.indexOf("}", closing);
			}
		}
		
	}
	
	private void parseExpresion(Tape tape, String label, LabelVertex sample) throws ParseError {
		TapePosition opening = tape.indexOf(label + "(");
		while(!opening.notFound()) {			
			TapePosition closing = tape.indexOf(")", opening);
			if (closing.notFound() || opening.getFirst() != closing.getFirst())
				throw new ParseError(label + " expresion invalid.");
			
			String expresion = tape.elementAt(opening.getFirst()).toString().substring(opening.getSecond() + 1 + label.length(), closing.getSecond());
			closing.setSecond(closing.getSecond()+1);
			closing = new TapePosition(tape.splitAt(closing)); 
			opening = new TapePosition(tape.splitAt(opening)); 
			
			Token paste = new Token(sample.getNew(expresion));
			tape.change(opening.getFirst(), paste);
			opening = tape.indexOf(label + "(");
		}
		return;
	}
	
	private void parseNumbers(Tape tape, String label) throws ParseError {
		parseExpresion(tape, label, new NumVertex(""));
	}
	
	private void parseVariables(Tape tape, String label) throws ParseError {
		parseExpresion(tape, label, new VarVertex(""));
	}
	
	private void parseConst(Tape tape) throws ParseError {
		tape.convertConst();
	}
	
	private void parseSpecial(Tape tape) throws ParseError {
		for (Entry<String, String> entry : specialSymbols.entrySet()) {
			String key = entry.getKey();
			String type = entry.getValue();
			if (type.equals("SUM_ITERATOR")) {
				TapePosition opening = tape.indexOf(key);
				TapePosition closing = new TapePosition(opening.getFirst(), opening.getSecond() + key.length());
				
				tape.splitAt(closing);
				tape.splitAt(opening);
				
				Token paste = new Token(new SumIteratorVertex(key));
				tape.change(opening.getFirst() + 1, paste);
			}
		}
	}
	
	private void parseBrackets(Tape tape) throws ParseError {
		TapePosition closing = tape.indexOf(")");
		
		while (!closing.notFound()) {
			TapePosition opening = tape.lastIndexOf("(", closing);
			if (opening.notFound()) 
				throw new ParseError("Closing bracket without opening one.");
			
			int to, from; // coordinates - inclusive
			
			String checker = tape.get(opening.getFirst()).toString();
			
			if (opening.getSecond() == 0) {
				tape.delete(opening);
				from = opening.getFirst();
			} else if (checker.endsWith("(")) {
				tape.delete(opening);
				from = opening.getFirst() + 1;
			} else {
				tape.delete(opening);
				from = opening.getFirst() + 1;
			}
			
			closing = tape.indexOf(")");
			
			if (closing.getSecond() == 0) {
				tape.delete(closing);
				to = closing.getFirst()-1;
			} else {
				tape.delete(closing);
				to = closing.getFirst();
			}
			
			Tape inside = new Tape(tape.subList(from, to+1));
			Token paste = new Token(parse(inside));
			tape.change(from, to+1, paste);
			
			closing = tape.indexOf(")");
		}
		
		TapePosition opening = tape.indexOf("(");
		if (!opening.notFound())
			throw new ParseError("Opening bracket without closing one.");
		
	}
		
	public Vertex parse(String me) throws ParseError {
		String[] relParts = me.split("!=|<|>|<=|>=|=");
		if (relParts.length > 2)
			throw new ParseError("Too many relation parts.");
		
		if (relParts.length > 1) { 
			String relation = "=";
			if (me.indexOf("!=") != -1) relation = "!=";
			else if (me.indexOf("<") != -1) relation = "<";
			else if (me.indexOf(">") != -1) relation = ">";
			else if (me.indexOf("<=") != -1) relation = "<=";
			else if (me.indexOf(">=") != -1) relation = ">=";
			
			Tape tape = new Tape();
			tape.add(new Token(relParts[0]));
			Vertex Vertex1 = parse(tape);
			
			tape = new Tape();
			tape.add(new Token(relParts[1]));
			Vertex Vertex2 = parse(tape);
			
			return new RelationVertex(relation, Vertex1, Vertex2);
		}
		
		// No relation in expression
		return parse(new Tape(new Token(me)));
	}
	
}
